/*
 * Copyright 2002-2016 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.security.web.context;

import java.io.IOException;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.springframework.core.log.LogMessage;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.GenericFilterBean;

/**
 * Populates the {@link SecurityContextHolder} with information obtained from the
 * configured {@link SecurityContextRepository} prior to the request and stores it back in
 * the repository once the request has completed and clearing the context holder. By
 * default it uses an {@link HttpSessionSecurityContextRepository}. See this class for
 * information <tt>HttpSession</tt> related configuration options.
 * <p>
 * This filter will only execute once per request, to resolve servlet container
 * (specifically Weblogic) incompatibilities.
 * <p>
 * This filter MUST be executed BEFORE any authentication processing mechanisms.
 * Authentication processing mechanisms (e.g. BASIC, CAS processing filters etc) expect
 * the <code>SecurityContextHolder</code> to contain a valid <code>SecurityContext</code>
 * by the time they execute.
 * <p>
 * This is essentially a refactoring of the old
 * <tt>HttpSessionContextIntegrationFilter</tt> to delegate the storage issues to a
 * separate strategy, allowing for more customization in the way the security context is
 * maintained between requests.
 * <p>
 * The <tt>forceEagerSessionCreation</tt> property can be used to ensure that a session is
 * always available before the filter chain executes (the default is <code>false</code>,
 * as this is resource intensive and not recommended).
 *
 * @author Luke Taylor
 * @since 3.0
 */
public class SecurityContextPersistenceFilter extends GenericFilterBean {

	static final String FILTER_APPLIED = "__spring_security_scpf_applied";

	private SecurityContextRepository repo;

	private boolean forceEagerSessionCreation = false;

	public SecurityContextPersistenceFilter() {
		this(new HttpSessionSecurityContextRepository());
	}

	public SecurityContextPersistenceFilter(SecurityContextRepository repo) {
		this.repo = repo;
	}

	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		doFilter((HttpServletRequest) request, (HttpServletResponse) response, chain);
	}

	/**
	 * 这个是差不多认证信息的最外层的过滤器
	 * 1.从session中获取context信息，如过不存在，创建一个空的context返回，然后放入当前线程中；
	 * 2.调用过滤器链；
	 * 3.fainlly中拿出context，然后清空当前线程缓存的对象（Threadlocal 要记得清空）；
	 * 4.然后讲context放入session（经过一系列的判断，符合条件就放入session）
	 * @param request
	 * @param response
	 * @param chain
	 * @throws IOException
	 * @throws ServletException
	 */
	private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		// ensure that filter is only applied once per request
		//一个request请求过来先设置一个FILTER_APPLIED，如果同一个request，发了多个过来
		//然后让其他请求直接调用后面的过滤器链接，也就是确保每个请求只能过滤一次
		if (request.getAttribute(FILTER_APPLIED) != null) {
			chain.doFilter(request, response);
			return;
		}
		//放入一个FILTER_APPLIED，确保每个请求只能执行一次过滤
		request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
		if (this.forceEagerSessionCreation) {
			HttpSession session = request.getSession();
			if (this.logger.isDebugEnabled() && session.isNew()) {
				this.logger.debug(LogMessage.format("Created session %s eagerly", session.getId()));
			}
		}
		//封装了一个holder对象，其实就是讲request和response封装成了一个对象而已，后面的调用就只用传一个参数而已
		HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request, response);
		/**
		 * 这里去获取一个contxt，SecurityContext在spring security是ThreadLocal<SecurityContext>
		 * 就是在当前线程中使用的，spring security在一个过滤器连得调用过程中都是一个线程内的，所以它巧妙的使用了
		 * Threalocal的特性将用户的认证信息放入Threadlocal中进行传输，最后使用完毕了，进行清空，就是在线程开始的时候
		 * 创建，在县城结束的时候它的使命也就完成了，就可以退出了，然后进行清空，然后在它退出“历史舞台“的时候，将它里面存储
		 * 的context拿出来放入session，所以也可以叫它是一个临时存储数据的仓库，而且这个仓库还是数据安全的
		 * 所以这个loadContext方法中先去session中获取，看session中是否有，如果有，就从session中获取这个context
		 * 如果没有，就创建一个空的（空的session存在的不一样的区别就是空的认证信息也是空的，session存入的认证信息是不为空的）
		 * 然后返回这个context，所以contextBeforeChainExecution返回的值100%不是空的
		 *
		 * 这里的repo的类型是HttpSessionSecurityContextRepository
		 */
		SecurityContext contextBeforeChainExecution = this.repo.loadContext(holder);
		try {
			//将获取的context放入当前线程的ThreadLocal<SecurityContext>中
			SecurityContextHolder.setContext(contextBeforeChainExecution);
			if (contextBeforeChainExecution.getAuthentication() == null) {
				//如果没有认证的请求，那么这里的这句日志会输出
				logger.debug("Set SecurityContextHolder to empty SecurityContext");
			}
			else {
				if (this.logger.isDebugEnabled()) {
					this.logger
							.debug(LogMessage.format("Set SecurityContextHolder to %s", contextBeforeChainExecution));
				}
			}
			/**
			 * 这里就开始调用过滤器链条上的其他过滤器
			 */
			chain.doFilter(holder.getRequest(), holder.getResponse());
		}
		finally {
			/**
			 * 等过滤器都调用完毕以后，会执行这里的diamante，这里的代码从ThreadLocal中获取出contextAfterChainExecution
			 * 这个时候如果用户在其他过滤器链条上进行了认证，那么这里从当前线程缓存中获取的context肯定是包含了
			 * 用户的认证信息
			 */
			SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
			// Crucial removal of SecurityContextHolder contents before anything else.
			//当前线程的执行已经马上结束了，所以这里清空ThreadLocal的缓存数据
			SecurityContextHolder.clearContext();
			//这里开始保存context，就是讲context放入session，但放入session是有条件的，首先认证信息不为空，并且认证信息
			//不是匿名用户的认证信息，然后session中是否有context，如果没有就放入session或者判断当前context是否有变更，如果
			//有，也重新放入session
			this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());
			//当前过滤器已经完成了，移除这个过滤器执行的条件
			request.removeAttribute(FILTER_APPLIED);
			this.logger.debug("Cleared SecurityContextHolder to complete request");
		}
	}

	public void setForceEagerSessionCreation(boolean forceEagerSessionCreation) {
		this.forceEagerSessionCreation = forceEagerSessionCreation;
	}

}
